package com.asen.commons.core.base.service.impl;

import com.asen.commons.annotation.ServiceAction;
import com.asen.commons.core.base.service.CoreService;
import com.asen.commons.constants.BaseConstant;
import com.asen.commons.core.base.domain.Core;
import com.asen.commons.core.base.enums.ActionTypeEnum;
import com.asen.commons.core.base.enums.BillStatusEnum;
import com.asen.commons.core.base.listener.GlobalEventCaster;
import com.asen.commons.core.base.listener.GlobalEventContext;
import com.asen.commons.exception.IllegalLogicException;
import com.asen.commons.exception.ServiceException;
import com.asen.commons.core.jdbc.dao.Dao;
import com.asen.commons.core.jdbc.factory.DaoFactory;
import com.asen.commons.core.listener.ServiceActionHolder;
import com.asen.commons.core.listener.ServiceActionListener;
import com.asen.commons.core.listener.ServiceContext;
import com.asen.commons.core.meta.dao.Condition;
import com.asen.commons.core.meta.entity.EntityObject;
import com.asen.commons.core.meta.entity.FieldObject;
import com.asen.commons.core.meta.loader.EntityObjectLoader;
import com.asen.commons.utils.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.ParameterizedType;
import java.math.BigDecimal;
import java.util.*;
import java.util.function.Consumer;

/**
 * 通用服务抽象类
 *
 * @author Gary
 * @version 1.0
 * @date 2018-11-29
 */
@Slf4j
public abstract class AbstractCoreServiceImpl<T extends Core> implements CoreService<T> {

    @Autowired
    private Dao dao;

    @Autowired
    private ServiceActionHolder serviceActionHolder;

    @Autowired
    private GlobalEventCaster globalEventCaster;

    protected void initAdd(T model) throws Exception {

    }

    protected void initUpdate(T model) throws Exception {

    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void add(T model) throws Exception {
        BaseAssert.notNull(model, "参数model不能为空");

        // 新增前业务逻辑处理
        initAdd(model);
        addValidate(model);
        beforeAdd(model);

        ServiceContext serviceContext = createServiceContext(model, ActionTypeEnum.ADD);
        beforeListener(serviceContext);

        // 执行新增操作
        doAdd(model);

        // 新增后业务逻辑处理
        afterAdd(model);
        afterListener(serviceContext);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void add(T model, Long id) throws Exception {
        BaseAssert.notNull(model, "参数model不能为空");
        model.setId(id);
        getDao().add(getModelClass(), model);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void batchAdd(Collection<T> list) throws Exception {
        if (CollectionUtils.isEmpty(list)) {
            return;
        }

        for (T model : list) {
            if (model == null) {
                continue;
            }

            add(model);
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void update(T model) throws Exception {
        BaseAssert.notNull(model, "参数model不能为空");

        // 更新前业务逻辑处理
        initUpdate(model);
        updateValidate(model);
        beforeUpdate(model);

        ServiceContext serviceContext = createServiceContext(model, ActionTypeEnum.UPDATE);
        beforeListener(serviceContext);

        doUpdate(model);

        // 更新后业务逻辑处理
        afterUpdate(model);
        afterListener(serviceContext);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateBy(T model, Condition condition) throws Exception {
        BaseAssert.notNull(model, "参数model不能为空");

        // 默认未删除
        getEntity().setSimpleValue(model, BaseConstant.IS_DELETED, Boolean.FALSE);
        getDao().updateBy(getModelClass(), model, condition);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updatePartial(T model, String... partialFields) throws Exception {
        // 默认未删除
        getEntity().setSimpleValue(model, BaseConstant.IS_DELETED, Boolean.FALSE);
        getDao().updatePartial(getModelClass(), model, partialFields);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void batchUpdate(Collection<T> list) throws Exception {
        if (CollectionUtils.isEmpty(list)) {
            return;
        }

        for (T model : list) {
            if (model == null) {
                continue;
            }

            update(model);
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void batchUpdatePartial(Collection<T> list, String... partialFields) throws Exception {
        for (T model : list) {
            if (model == null) {
                continue;
            }

            updatePartial(model, partialFields);
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void remove(T model) throws Exception {
        BaseAssert.notNull(model, "参数model不能为空");
        BaseAssert.check(getEntity().hasField(BaseConstant.IS_DELETED), "不支持remove方法！");

        if (IdUtils.isEmpty(model.getId())) {
            return;
        }

        // 删除前查询获取对象
        model = get(model.getId());
        if (model == null) {
            throw new IllegalLogicException(getEntity().getAlias() + "删除失败,对象已不存在");
        }

        // 删除前业务逻辑处理
        removeValidate(model);
        beforeRemove(model);

        // 监听
        ServiceContext serviceContext = createServiceContext(model, ActionTypeEnum.REMOVE);
        beforeListener(serviceContext);

        // 删除操作
        doRemove(model);

        afterListener(serviceContext);

        // 删除后业务逻辑处理
        afterRemove(model);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void removeBy(Long id) throws Exception {
        if (IdUtils.isEmpty(id)) {
            return;
        }

        // 没有删除标记时，不做任何处理
        if (!getEntity().hasField(BaseConstant.IS_DELETED)) {
            return;
        }

        // 逻辑删除
        T model = getModelClass().newInstance();
        getEntity().setSimpleValue(model, "id", id);
        getEntity().setSimpleValue(model, BaseConstant.IS_DELETED, Boolean.TRUE);
        getDao().updatePartialBy(getModelClass(), model, new String[]{BaseConstant.IS_DELETED}, "id");
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void removeBy(Condition condition) throws Exception {
        removeBy(condition, false);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void removeBy(Condition condition, boolean updateTime) throws Exception {
        if (condition == null) {
            return;
        }

        // 没有删除标记时，不做任何处理
        if (!getEntity().hasField(BaseConstant.IS_DELETED)) {
            return;
        }

        Condition removeCondition = new Condition();
        removeCondition.select(BaseConstant.IS_DELETED);
        if (updateTime) {
            removeCondition.select(BaseConstant.UPDATE_BY_ID);
            removeCondition.select(BaseConstant.UPDATE_TIME);
        }
        removeCondition.setFilterRelation(condition.getFilterRelation());
        removeCondition.setFilterGroups(condition.getFilterGroups());
        removeCondition.setFilterItems(condition.getFilterItems());

        // 条件删除
        T model = getModelClass().newInstance();
        getEntity().setFieldValue(model, BaseConstant.IS_DELETED, Boolean.TRUE);
        if (updateTime) {
            // TODO
            //getEntity().setFieldValue(model, BaseConstant.UPDATE_BY_ID, UserUtils.getUserId());
            getEntity().setFieldValue(model, BaseConstant.UPDATE_TIME, new Date());
        }
        getDao().updateBy(getModelClass(), model, removeCondition);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void batchRemove(Collection<T> list) throws Exception {
        if (list == null || list.isEmpty()) {
            return;
        }

        if (!getEntity().hasField(BaseConstant.IS_DELETED)) {
            return;
        }

        for (T model : list) {
            if (model == null) {
                continue;
            }

            remove(model);
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void removeCompletely(T model) throws Exception {
        if (model == null || model.getId() == null || model.getId() == 0L) {
            return;
        }

        getDao().delete(getModelClass(), model);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void removeByCompletely(Condition condition) throws Exception {
        if (condition == null) {
            return;
        }

        getDao().deleteBy(getModelClass(), condition);
    }

    @Override
    public T get(Long id) throws Exception {
        if (IdUtils.isEmpty(id)) {
            return null;
        }

        T model = doGet(id);

        afterGet(model);

        return model;
    }

    @Override
    public T getBy(Condition condition) throws Exception {
        if (condition == null) {
            return null;
        }
        condition.equal(BaseConstant.IS_DELETED, Boolean.FALSE);
        condition.limit(1);
        List<T> list = getDao().getList(getModelClass(), condition);
        return list.isEmpty() ? null : list.get(0);
    }

    @Override
    public List<T> getAll() throws Exception {
        Condition condition = new Condition();
        condition.equal(BaseConstant.IS_DELETED, Boolean.FALSE);
        List<T> list = getList(condition);
        return list;
    }

    @Override
    public List<T> getAllByPage(int pageNum, int pageSize) throws Exception {
        Condition condition = new Condition();
        condition.setPage(true);
        condition.setPageNum(pageNum);
        condition.setPageSize(pageSize);
        List<T> list = getList(condition);
        return list;
    }

    @Override
    public List<T> getList(Condition condition) throws Exception {
        if (condition == null) {
            return null;
        }
        // 组织隔离 原逻辑
        // filterDataPermission(condition);

        condition.equal(BaseConstant.IS_DELETED, Boolean.FALSE);
        return getDao().getList(getModelClass(), condition);
    }

    @Override
    public List<T> getListIsolation(Condition condition) throws Exception {
        //IsolationUtils.isolationFilter(condition, getModelClass());
        return this.getList(condition);
    }

    @Override
    public int count() throws Exception {
        Condition condition = new Condition();
        condition.equal(BaseConstant.IS_DELETED, Boolean.FALSE);
        return getDao().getCount(getModelClass(), condition);
    }

    @Override
    public int count(Condition condition) throws Exception {
        if (condition == null) {
            return 0;
        }
        // 组织隔离 原逻辑
        // filterDataPermission(condition);

        condition.equal(BaseConstant.IS_DELETED, Boolean.FALSE);
        return getDao().getCount(getModelClass(), condition);
    }

    @Override
    public int countIsolation(Condition condition) throws Exception {
        //IsolationUtils.isolationFilter(condition, getModelClass());
        return this.count(condition);
    }

    @Override
    public boolean exist(Long id) throws Exception {
        if (id == null || id == 0L) {
            return false;
        }

        Condition condition = new Condition();
        condition.equal(BaseConstant.IS_DELETED, Boolean.FALSE);
        condition.equal("id", id);
        return getDao().getCount(getModelClass(), condition) > 0;
    }

    @Override
    public T getIncludeDeleted(Long id) throws Exception {
        if (id == null || id == 0L) {
            return null;
        }

        Condition condition = new Condition();
        condition.equal("id", id);
        List<T> list = getDao().getList(getModelClass(), condition);
        T model = null;
        if (list.size() > 0) {
            model = list.get(0);
        }

        return model;
    }

    @Override
    public List<T> getListIncludeDeleted(Condition condition) throws Exception {
        if (condition == null) {
            return null;
        }

        return getDao().getList(getModelClass(), condition);
    }

    @Override
    public boolean existIncludeDeleted(Long id) throws Exception {
        if (IdUtils.isEmpty(id)) {
            return false;
        }

        Condition condition = new Condition();
        condition.equal("id", id);
        return getDao().getCount(getModelClass(), condition) > 0;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public int execSql(String sql) throws Exception {
        return getDao().execSql(sql);
    }

    @Override
    public List<Map<String, Object>> query(String sql) throws Exception {
        return getDao().query(sql);
    }

    @Override
    public <K> List<K> query(String sql, Class<K> clazz) throws Exception {
        List<K> result = null;

        List<Map<String, Object>> list = this.query(sql);
        if (list != null && list.size() > 0) {
            String jsonStr = JsonMapper.list2json(list);
            result = JsonMapper.json2list(jsonStr, clazz);
            //String jsonStr = JSON.toJSONString(list);
            //result = JSON.parseArray(jsonStr, clazz);
        }

        return result;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateStatus(Long id, BillStatusEnum status) throws Exception {
        BaseAssert.check(IdUtils.isNotEmpty(id), "参数id不能为空");
        BaseAssert.notNull(status, "参数状态不能为空");

        if (!getEntity().hasField("status")) {
            return;
        }

        T model = getModelClass().newInstance();
        // TODO
        //Long userId = UserUtils.getUserId();
        Long userId = -1L;
        Date date = new Date();
        model.setId(id);
        getEntity().setFieldValue(model, "status", status.getValue());
        if (EnumUtils.equal(BillStatusEnum.AUDITED, status)) {
            getEntity().setFieldValue(model, "auditById", userId);
            getEntity().setFieldValue(model, "auditTime", date);
        } else {
            getEntity().setFieldValue(model, "auditById", null);
            getEntity().setFieldValue(model, "auditTime", null);
        }
        getEntity().setFieldValue(model, "updateById", userId);
        getEntity().setFieldValue(model, "updateTime", date);
        updatePartial(model, "status", "auditById", "auditTime", "updateById", "updateTime");

        GlobalEventContext globalEventContext = new GlobalEventContext();
        globalEventContext.setEventType(this.getClass().getName() + "#updateStatus");
        globalEventContext.setBillId(model.getId());
        globalEventContext.setBillType(getModelClass().getName());
        globalEventContext.setData(JsonMapper.obj2map(model));
        globalEventCaster.castAsync(globalEventContext);
    }

    @Override
    public void updateDecimalField(Map<Long, BigDecimal> map, String fieldName) throws Exception {
        updateDecimalField(map, fieldName, null, null);
    }

    @Override
    public void updateDecimalField(Map<Long, BigDecimal> map, String fieldName, String totalFieldName, String innerFieldName) throws Exception {
        if (map == null || map.size() == 0) {
            return;
        }
        if (StringUtils.isEmpty(fieldName)) {
            return;
        }
        EntityObject entityObject = getEntity();
        FieldObject fieldObject = entityObject.getFieldObject(fieldName);
        List<Long> ids = new ArrayList<>(map.keySet());
        Condition condition = new Condition();
        condition.in("id", ids);
        List<T> list = getList(condition);
        if (CollectionUtils.isEmpty(list)) {
            throw new IllegalLogicException("反写异常.id对应数据未找到");
        }
        for (T model : list) {
            BigDecimal oriAmount = (BigDecimal) fieldObject.getValue(model);
            fieldObject.setValue(model, DecimalUtils.add(oriAmount, map.get(model.getId())));
            Consumer<? super T> innerFunction = getInnerFunction(model, fieldName, totalFieldName, innerFieldName);
            if (innerFunction != null) {
                innerFunction.accept(model);
            } else if (StringUtils.isNotBlank(totalFieldName) && StringUtils.isNotBlank(innerFieldName)) {
                FieldObject totalFieldObject = entityObject.getFieldObject(totalFieldName);
                FieldObject innerFieldObject = entityObject.getFieldObject(innerFieldName);

                BigDecimal amountTotal = (BigDecimal) totalFieldObject.getValue(model);
                BigDecimal amount = (BigDecimal) fieldObject.getValue(model);

                innerFieldObject.setValue(model, DecimalUtils.subtract(amountTotal, amount));
            }
            Consumer<? super T> checkFunction = getCheckFunction(model, map, fieldName, totalFieldName, innerFieldName);
            if (checkFunction != null) {
                // 校验
                checkFunction.accept(model);
            }
        }
        if (StringUtils.isNotBlank(totalFieldName) && StringUtils.isNotBlank(innerFieldName)) {
            batchUpdatePartial(list, fieldName, totalFieldName, innerFieldName);
        } else {
            batchUpdatePartial(list, fieldName);
        }
    }

    /**
     * 自定义剩余字段赋值
     */
    protected Consumer<? super T> getInnerFunction(T model, String fieldName, String totalFieldName, String innerFieldName) {
        return null;
    }

    /**
     * 自定义检验方法
     */
    protected Consumer<? super T> getCheckFunction(T model, Map<Long, BigDecimal> map, String fieldName, String totalFieldName, String innerFieldName) {
        return null;
    }

    @Override
    public List<T> getListIds(List<Long> ids) throws Exception {
        if (CollectionUtils.isEmpty(ids)) {
            return null;
        }
        Condition condition = new Condition();
        condition.in("id", ids);
        return getList(condition);
    }

    @Override
    public void buildCommonFilter(Condition condition) throws Exception {
        // 组织隔离 新逻辑
        // buildConditionIsolation(condition);
        // IsolationUtils.buildConditionIsolation(condition, getModelClass());
        //IsolationUtils.isolationFilter(condition, getModelClass());
    }

    protected void addValidate(T model) throws Exception {

    }

    protected void beforeAdd(T model) throws Exception {

    }

    protected void doAdd(T model) throws Exception {
        if (IdUtils.isEmpty(model.getId())) {
            model.setId(IdUtils.genIdBySnowflake());
        }

        getEntity().setSimpleValue(model, BaseConstant.IS_DELETED, Boolean.FALSE);
        getDao().add(getModelClass(), model);
    }

    protected void afterAdd(T model) throws Exception {

    }

    protected void updateValidate(T model) throws Exception {

    }

    protected void beforeUpdate(T model) throws Exception {

    }

    protected void doUpdate(T model) throws Exception {
        // 执行更新操作（本操作不执行逻辑删除，逻辑删除请使用remove方法）
        BaseAssert.notNull(model, "参数对象不能为空！");
        BaseAssert.notEmpty(model.getId(), "ID不合法！");
        getEntity().setSimpleValue(model, BaseConstant.IS_DELETED, Boolean.FALSE);
        getDao().update(getModelClass(), model);
    }

    protected void afterUpdate(T model) throws Exception {

    }

    protected void removeValidate(T model) throws Exception {

    }

    protected void beforeRemove(T model) throws Exception {

    }

    protected void doRemove(T model) throws Exception {
        // TODO
        BaseAssert.notNull(model, "参数对象不能为空！");
        BaseAssert.notEmpty(model.getId(), "ID不合法！");
        getEntity().setSimpleValue(model, BaseConstant.IS_DELETED, Boolean.TRUE);
        //getEntity().setSimpleValue(model, BaseConstant.UPDATE_BY_ID, UserUtils.getUserId());
        getEntity().setSimpleValue(model, BaseConstant.UPDATE_TIME, new Date());
        getDao().updatePartial(getModelClass(), model, BaseConstant.IS_DELETED, BaseConstant.UPDATE_BY_ID, BaseConstant.UPDATE_TIME);
    }

    protected void afterRemove(T model) throws Exception {

    }

    protected T doGet(Long id) throws Exception {
        Condition condition = new Condition();
        condition.equal("id", id);
        condition.equal(BaseConstant.IS_DELETED, Boolean.FALSE);
        condition.limit(1);
        List<T> list = getDao().getList(getModelClass(), condition);
        T model = null;
        if (list.size() > 0) {
            model = list.get(0);
        }
        return model;
    }

    protected void afterGet(T model) throws Exception {

    }

    protected ServiceContext createServiceContext(T model, ActionTypeEnum actionType) throws Exception {
        ServiceContext serviceContext = new ServiceContext();
        serviceContext.setBillType(getModelClass().getName());
        serviceContext.setActionType(actionType);
        serviceContext.setModel(model);
        serviceContext.setServiceBean(this);
        return serviceContext;
    }

    protected void beforeListener(ServiceContext<T> serviceContext) throws Exception {
        List<ServiceActionListener> listeners = getListeners();
        if (CollectionUtils.isEmpty(listeners)) {
            return;
        }

        for (ServiceActionListener listener : listeners) {
            if (listener.getClass().isAnnotationPresent(ServiceAction.class)) {
                ServiceAction actionService = listener.getClass().getAnnotation(ServiceAction.class);

                if (EnumUtils.equal(ActionTypeEnum.NONE, serviceContext.getActionType())) {
                    continue;
                }

                if (EnumUtils.notEqual(ActionTypeEnum.ALL, serviceContext.getActionType())
                        && EnumUtils.notContains(actionService.actions(), serviceContext.getActionType())) {
                    continue;
                }
            }

            log.info("ServiceActionListener {}.before(..) executing", listener.getClass().getName());
            listener.before(serviceContext);
        }
    }

    protected void afterListener(ServiceContext<T> serviceContext) throws Exception {
        List<ServiceActionListener> listeners = getListeners();
        if (CollectionUtils.isEmpty(listeners)) {
            return;
        }

        for (ServiceActionListener listener : listeners) {
            if (listener.getClass().isAnnotationPresent(ServiceAction.class)) {
                ServiceAction actionService = listener.getClass().getAnnotation(ServiceAction.class);

                if (EnumUtils.equal(ActionTypeEnum.NONE, serviceContext.getActionType())) {
                    continue;
                }

                if (EnumUtils.notEqual(ActionTypeEnum.ALL, serviceContext.getActionType())
                        && EnumUtils.notContains(actionService.actions(), serviceContext.getActionType())) {
                    continue;
                }
            }

            log.info("ServiceActionListener {}.after(..) executing", listener.getClass().getName());
            listener.after(serviceContext);
        }
    }

    private List<ServiceActionListener> listeners;

    protected List<ServiceActionListener> getListeners() throws Exception {
        if (listeners == null) {
            synchronized (lock) {
                if (listeners == null) {
                    listeners = serviceActionHolder.getListeners(this.getModelClass());
                }
            }
        }
        return listeners;
    }

    private Object lock = new Object();

    public Dao getDao() {
        if (dao == null) {
            synchronized (lock) {
                if (dao == null) {
                    dao = SpringUtil.getBean(Dao.class);
                    if (dao == null) {
                        dao = DaoFactory.createMySqlDao();
                    }
                }
            }
        }
        return dao;
    }

    @Override
    public Class<T> getBillType() {
        return getModelClass();
    }

    public void setDao(Dao dao) {
        this.dao = dao;
    }

    private Class<T> modelClazz;

    @SuppressWarnings("unchecked")
    protected Class<T> getModelClass() {
        if (modelClazz != null) {
            return modelClazz;
        }

        Class<?> typeClazz = this.getClass();

        do {
            if (typeClazz.getGenericSuperclass() instanceof ParameterizedType) {
                ParameterizedType superClassType = (ParameterizedType) typeClazz.getGenericSuperclass();
                if (!ArrayUtils.isEmpty(superClassType.getActualTypeArguments())) {
                    if (superClassType.getActualTypeArguments()[0] instanceof Class) {
                        modelClazz = (Class<T>) superClassType.getActualTypeArguments()[0];
                    }
                    break;
                }
            } else {
                typeClazz = typeClazz.getSuperclass();
            }

            if (typeClazz.equals(Object.class)) {
                break;
            }
        } while (!ParameterizedType.class.isAssignableFrom(typeClazz));

        if (modelClazz == null) {
            throw new ServiceException(String.format("服务类[%s]未实现泛型", this.getClass().getName()));
        }

        return modelClazz;

    }

    private EntityObject entityObject;

    protected EntityObject getEntity() {
        if (entityObject != null) {
            return entityObject;
        }

        synchronized (lock) {
            if (entityObject == null) {
                entityObject = EntityObjectLoader.getInstance().load(getModelClass());
            }
        }

        return entityObject;
    }
}
